﻿using DotNet.Utilities;
using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace zhihu {
    class ZhihuHelper {
        /// <summary>
        /// 线程锁
        /// </summary>
        private static object _threadLock = new object();

        /// <summary>
        /// 采集问题,并写入数据库 返回采集到的个数,出错返回-1
        /// </summary>
        /// <param name="Cookies"></param>
        /// <param name="listPageUrl"></param>
        public static int GetQuestionByList(string cookies, string listPageUrl, int nAnswerTimes) {
            // 打开页面
            HttpItem htRequset = new HttpItem() {
                URL = listPageUrl,
                WebProxy = null,
                Cookie = cookies,
                UserAgent = "null"
            };
            HttpResult htResponse = new HttpHelper().GetHtml(htRequset);

            // 解析页面
            HtmlAgilityPack.HtmlDocument hdoc = new HtmlAgilityPack.HtmlDocument();
            hdoc.LoadHtml(htResponse.Html);
            if (hdoc.ParseErrors != null && hdoc.ParseErrors.Any()) {
                // MessageBox.Show("HTML解析失败.");
                // return false;
                return -1;
            }

            // 开始用xpath提取页面的内容
            HtmlNodeCollection questions =
                    // hdoc.DocumentNode.SelectNodes(@"/html/body/div[3]/div[1]/div/div[3]/div/div");
                    hdoc.DocumentNode.SelectNodes(@"//*[@id=""zh-topic-questions-list""]/div");
            // questions 有可能是null
            // 一般情况下是分类太小 问题数不够导致的 需要有个错误返回
            if (questions == null) return -1;

            // 计数
            int nCount = 0;

            // 遍历问题DIV,把问题和链接什么的写入数据库
            foreach (var question in questions) {
                HtmlNode answersNode = question.SelectSingleNode(string.Concat(question.XPath,
                    // @"/div/a[@class='zg-link-gray-normal meta-item']"));
                    @"/div[2]/a[1]"));

                // answersNode 可能是Null 不知道为什么
                if (answersNode == null) continue;

                Match mt = Regex.Match(answersNode.InnerText, @"(\d+) 个回答", RegexOptions.Singleline);
                // 匹配不成功表示没有答案 跳过
                if (!mt.Success) continue;
                // 回答数小于 nAnswerTimes 个 跳过
                int nAnswers = Convert.ToInt32(mt.Groups[1].Value);
                if (nAnswers < nAnswerTimes) continue;

                HtmlNode linkNode = question.SelectSingleNode(string.Concat(question.XPath, "/h2/a"));
                // 把问题写进数据库
                string href = linkNode.Attributes["href"].Value;
                int questionId = int.Parse(href.Substring(href.LastIndexOf('/') + 1));
                string questionUrl = string.Concat("http://www.zhihu.com", href);
                const string insertCommand =
                    "insert into T_question (question, questionId, questionUrl) VALUES(@q, @qid, @qurl)";
                SQLiteHelper.ExecuteNonQuery(GlobalInfo.ConnectionString, insertCommand,
                    linkNode.InnerHtml, questionId, questionUrl);

                nCount++;
            }
            return nCount;
        }

        /// <summary>
        /// 采集回答,并写入数据库,多线程安全
        /// </summary>
        /// <param name="cookies"></param>
        /// <param name="questionPageUrl"></param>
        public static void GetAnswerByQuestionPage(string cookies, string questionPageUrl, int questionId) {
            try {
                // 打开页面
                HttpItem hAnswerItem = new HttpItem() {
                    URL = questionPageUrl,
                    WebProxy = null,
                    Cookie = cookies,
                    UserAgent = "null"
                };
                HttpResult hAnswerRes = new HttpHelper().GetHtml(hAnswerItem);

                // 计算公式：score =vote / (5+answer_len^2/10) 
                // 提取页面的各个回答的信息
                HtmlAgilityPack.HtmlDocument hdoc = new HtmlAgilityPack.HtmlDocument();
                hdoc.LoadHtml(hAnswerRes.Html);
                var questContentNote = hdoc.DocumentNode.SelectSingleNode(@"/html/body/div[5]/div[1]/div/div[4]/div");
                // 问题已关闭的情况 questContentNote就是null
                if (questContentNote == null) return;

                // 问题的详细内容
                string questContent = questContentNote.InnerHtml;
                // 所有回答的节点 (如果没有回答的话 这里会是null 下面遍历就会异常 不过没关系 不存在这个问题了)
                // var answerNodes = hdoc.DocumentNode.SelectNodes(@"/html/body/div[5]/div[1]/div/div[8]/div");
                var answerNodes = hdoc.DocumentNode.SelectNodes("//*[@id=\"zh-question-answer-wrap\"]/div");
                // 遍历所有回答,计算分数
                List<AnswerInfo> answerList = new List<AnswerInfo>();
                foreach (var answerNode in answerNodes) {
                    //// 赞同数
                    //var voteNode = answerNode.SelectSingleNode(string.Concat(answerNode.XPath,
                    //    @"/div[1]/button[1]/span[2]"));
                    //// 回答的详细内容
                    //var answerContentNode = answerNode.SelectSingleNode(string.Concat(answerNode.XPath,
                    //    @"/div[3]/div"));

                    // 赞同数
                    var voteNode = answerNode.SelectSingleNode(string.Concat(answerNode.XPath,
                        @"/div[1]/button[1]/span[2]"));
                    // 回答的详细内容
                    var answerContentNode = answerNode.SelectSingleNode(string.Concat(answerNode.XPath,
                        @"/div[3]/div[2]"));

                    // 回答的详细内容有可能是null 比如违规的会被知乎官方屏蔽
                    if (answerContentNode == null) continue;

                    AnswerInfo ai = new AnswerInfo();
                    ai.Vote = int.Parse(voteNode.InnerText);
                    ai.AnswerHtml = answerContentNode.InnerHtml;
                    ai.AnswerLength = ai.AnswerHtml.Length;
                    ai.Score = Convert.ToDouble(ai.Vote) / (5 + Math.Pow(ai.AnswerLength, 2) / 10);
                    answerList.Add(ai);
                }
                // 排序
                answerList.Sort(AnswerInfo.CompareByScore);
                var maxScoreAnswer = answerList.ElementAt(0);
                // 写入数据库
                const string insertCommand =
                    @"INSERT INTO T_answer (answer, questionId, score, questionContent) VALUES (@a, @qid, @socre, @qc)";
                // 多线程同步
                lock (_threadLock) {
                    SQLiteHelper.ExecuteNonQuery(GlobalInfo.ConnectionString, insertCommand,
                    maxScoreAnswer.AnswerHtml, questionId, maxScoreAnswer.Score, questContent);
                }
            }
            catch (Exception ex) {
                LogHelper.Info("ZhihuHelper.GetAnswerByQuestionPage Error");
                LogHelper.Info(ex.ToString());
            }
        }

        /// <summary>
        /// 抓问题
        /// </summary>
        /// <param name="nGrabTimes">要抓的个数</param>
        /// <param name="nAnswerTimes">只抓回答数大于nAnswerTimes的问题</param>
        /// <returns></returns>
        public static bool GrabQuestion(int nGrabTimes, int nAnswerTimes = 5) {
            int nCurTimes = 0;
            int nPage = 1;
            while (true) {
                HttpItem htRequset = new HttpItem() {
                    URL = string.Concat("http://www.zhihu.com/topic/19805970/questions?page=", nPage),
                    WebProxy = null,
                    Cookie = GlobalInfo.LoginCookies
                };
                HttpResult htResponse = new HttpHelper().GetHtml(htRequset);
                nPage++; // 翻页

                HtmlAgilityPack.HtmlDocument hdoc = new HtmlAgilityPack.HtmlDocument();
                hdoc.LoadHtml(htResponse.Html);
                if (hdoc.ParseErrors != null && hdoc.ParseErrors.Any()) {
                    // MessageBox.Show("HTML解析失败.");
                    return false;
                }

                // 开始用xpath提取页面的内容
                // 如果失败的话要重新写xpath表达式
                HtmlNodeCollection questions =
                    hdoc.DocumentNode.SelectNodes(@"/html/body/div[3]/div[1]/div/div[3]/div/div");
                // HtmlNode question = questions[0].SelectSingleNode(questions[0].XPath + "/h2/a");
                // 下面这种写法比较容易理解
                // HtmlNode question = questions[0].SelectSingleNode("//h2/a");

                // questions 有可能是null
                // 一般情况下是分类太小 问题数不够导致的 需要有个错误返回

                // 遍历问题DIV,把问题和链接什么的写入数据库
                foreach (var question in questions) {
                    HtmlNode answersNode = question.SelectSingleNode(string.Concat(question.XPath,
                        "/div/a[@class='zg-link-gray-normal meta-item']"));
                    Match mt = Regex.Match(answersNode.InnerText, @"(\d+) 个回答", RegexOptions.Singleline);
                    // 匹配不成功表示没有答案 跳过
                    if (!mt.Success) continue;
                    // 回答数小于nAnswerTimes个 跳过
                    int nAnswers = Convert.ToInt32(mt.Groups[1].Value);
                    if (nAnswers < nAnswerTimes) continue;

                    HtmlNode linkNode = question.SelectSingleNode(string.Concat(question.XPath, "/h2/a"));
                    // 把问题写进数据库
                    string href = linkNode.Attributes["href"].Value;
                    int questionId = int.Parse(href.Substring(href.LastIndexOf('/') + 1));
                    string questionUrl = string.Concat("http://www.zhihu.com", href);
                    const string insertCommand =
                        "insert into T_question (question, questionId, questionUrl) VALUES(@q, @qid, @qurl)";
                    SQLiteHelper.ExecuteNonQuery(GlobalInfo.ConnectionString, insertCommand,
                        linkNode.InnerHtml, questionId, questionUrl);

                    nCurTimes++; // 计次
                }
                if (nCurTimes >= nGrabTimes) break; // 跳出检测
            }
            
            return true;
        }

        /// <summary>
        /// 抓答案
        /// </summary>
        public static void GrabAnswer() {
            // 从数据库中拿到问题的ID和Url
            // const string selectCommand = @"SELECT questionId,questionUrl FROM T_question";
            // group by 可以去重复
            const string selectCommand = @"SELECT questionId,questionUrl FROM T_question GROUP BY questionId";
            DataSet executeDataSet =
                SQLiteHelper.ExecuteDataset(GlobalInfo.ConnectionString, selectCommand, null);

            // 遍历并把数据转化成字典
            DataTable dt = executeDataSet.Tables[0];
            //foreach (DataRow row in dt.Rows){
            //    dicQuestion.Add(row[0] as int?, row[1] as string);
            //}
            Dictionary<int, string> dicQuestion = dt.Rows.Cast<DataRow>()
                .ToDictionary(row => row[0] != null ? int.Parse(row[0].ToString()) : 0, row => row[1] as string);

            foreach (var item in dicQuestion) {
                // 抓问题页面
                HttpItem hAnswerItem = new HttpItem() {
                    URL = item.Value,
                    WebProxy = null,
                    Cookie = GlobalInfo.LoginCookies
                };
                HttpResult hAnswerRes = new HttpHelper().GetHtml(hAnswerItem);

                // 计算公式：score =vote / (5+answer_len^2/10) 
                // 提取页面的各个回答的信息
                HtmlAgilityPack.HtmlDocument hdoc = new HtmlAgilityPack.HtmlDocument();
                hdoc.LoadHtml(hAnswerRes.Html);
                var questContentNote = hdoc.DocumentNode.SelectSingleNode(@"/html/body/div[5]/div[1]/div/div[4]/div");
                // 问题已关闭的情况 questContentNote就是null
                if (questContentNote == null) continue;
                
                // 问题的详细内容
                string questContent = questContentNote.InnerHtml;
                // 所有回答的节点 (如果没有回答的话 这里会是null 下面遍历就会异常 不过没关系 不存在这个问题了)
                var answerNodes = hdoc.DocumentNode.SelectNodes(@"/html/body/div[5]/div[1]/div/div[8]/div");
                // 遍历所有回答,计算分数
                List<AnswerInfo> answerList = new List<AnswerInfo>();
                foreach (var answerNode in answerNodes) {
                    // 赞同数
                    var voteNode = answerNode.SelectSingleNode(string.Concat(answerNode.XPath,
                        @"/div[1]/button[1]/span[2]"));
                    // 回答的详细内容
                    var answerContentNode = answerNode.SelectSingleNode(string.Concat(answerNode.XPath,
                        @"/div[3]/div"));

                    // 回答的详细内容有可能是null 比如违规的会被知乎官方屏蔽
                    if (answerContentNode == null) continue;

                    AnswerInfo ai = new AnswerInfo();
                    ai.Vote = int.Parse(voteNode.InnerText);
                    ai.AnswerHtml = answerContentNode.InnerHtml;
                    ai.AnswerLength = ai.AnswerHtml.Length;
                    ai.Score = Convert.ToDouble(ai.Vote) / (5 + Math.Pow(ai.AnswerLength, 2) / 10);
                    answerList.Add(ai);
                }
                // 排序
                answerList.Sort(AnswerInfo.CompareByScore);
                var maxScoreAnswer = answerList.ElementAt(0);
                // 写入数据库
                const string insertCommand =
                    @"INSERT INTO T_answer (answer, questionId, score, questionContent) VALUES (@a, @qid, @socre, @qc)";
                SQLiteHelper.ExecuteNonQuery(GlobalInfo.ConnectionString, insertCommand,
                    maxScoreAnswer.AnswerHtml, item.Key, maxScoreAnswer.Score, questContent);

            }
        }

        /// <summary>
        /// 提取数据库中得分前 nExporeCount 名
        /// </summary>
        public static List<ViewInfo> GetTopOfDataBase(int nExportCount) {
            return GetTopOfDataBase(0, nExportCount);
        }

        /// <summary>
        /// 提取数据库中得分 第nExportRange1到nExportRange2范围
        /// </summary>
        public static List<ViewInfo> GetTopOfDataBase(int nExportRange1, int nExportRange2) {
            const string queryCommand = @"SELECT T_answer.answer, T_answer.questionContent, T_question.question, T_question.questionUrl FROM T_answer  JOIN T_question ON T_answer.questionId = T_question.questionId ORDER BY T_answer.score DESC LIMIT @left,@right";
            DataSet ds = SQLiteHelper.ExecuteDataset(GlobalInfo.ConnectionString, queryCommand,
                new object[] { nExportRange1, nExportRange2 - nExportRange1 });
            DataTable dt = ds.Tables[0];

            List<ViewInfo> listViewInfo = new List<ViewInfo>();
            foreach (DataRow row in dt.Rows) {
                ViewInfo vi = new ViewInfo();
                vi.Answer = row[0].ToString();
                vi.QuestionContent = row[1].ToString();
                vi.Question = row[2].ToString();
                vi.QuestionUrl = row[3].ToString();
                listViewInfo.Add(vi);
            }
            return listViewInfo;
        }

        /// <summary>
        /// 提取数据库中的所有问题
        /// </summary>
        /// <returns></returns>
        public static Dictionary<int, string> GetQuestionOfDataBase() {
            // 从数据库中拿到问题的ID和Url
            // group by 可以去重复
            const string selectCommand = @"SELECT questionId,questionUrl FROM T_question GROUP BY questionId";
            DataSet executeDataSet =
                SQLiteHelper.ExecuteDataset(GlobalInfo.ConnectionString, selectCommand, null);

            // 遍历并把数据转化成字典
            DataTable dt = executeDataSet.Tables[0];
            //foreach (DataRow row in dt.Rows){
            //    dicQuestion.Add(row[0] as int?, row[1] as string);
            //}
            Dictionary<int, string> dicQuestion = dt.Rows.Cast<DataRow>()
                .ToDictionary(row => row[0] != null ? int.Parse(row[0].ToString()) : 0, row => row[1] as string);
            return dicQuestion;
        }
    }
}




